Mangled Names

You’ve probably seen them buried deep inside a map file: an identifier that kind of looks like one of your function names after being run through a blender.

If the following function appears in a C source file,

void StraightCFunction (int* pInput, const char* pszOutput)

the following symbol appears in the object and map files:

_StraightCFunction

This function is suitable for inclusion in the module definition file. Take the same function and insert it into a CPP source file, and you get the following:

?StraightCFunction@@YAXPAHPBD@Z

What’s the stuff following the name? This is compiler-speak for the argument list and return types. In standard C, it is illegal to have two functions or variables with the same name (unless one or both are static scope). So the compiler merely refers to the symbol by its textual name, with an underscore prepended. C++, however, supports function overloading. Function overloading enables you to have two externally visible functions with the same name, provided their argument lists are different. Thus, in a C++ file, you could have the following:

int Addition (int nOperand1, int nOperand2);
long Addition (long lOperand1, long lOperand2);

To avoid name conflicts, and to ensure that the linker enforces strong typing, the compiler encodes (mangles) the parameter types, return type, and any qualifiers (such as const) into a single name.

This phenomenon is what leads to object and library incompatibilities between compiler vendors because each vendor uses a slightly different encoding scheme. And although compiler geeks try to put a positive spin on it (they call it name decoration), it complicates the DLL development process immensely. Listing 33.1 shows a sample module definition file that includes compiler-mangled names.

Listing 33.1 Sample Module Definition File


;
; Sample Module Defintion File
;
LIBRARY      “afxsamp1”
DESCRIPTION  ‘Afx Sample Windows Dynamic Link Library’
EXPORTS
?StraightCPPFunction@@YAXPAHPBD@Z @1000 NONAME ; Exported by ordinal
StraightCFunction                @1001 NONAME  ; Exported by ordinal
?Addition@@YAHHH@Z                             ; Exported by name
?Addition@@YAJJJ@Z                             ; Exported by name

In this example, the StraightCFunction and StraightCPPFunction are exported by ordinal. The @xxxx assigns a unique numeric value to each identifier; the NONAME qualifier explicitly removes the textual name from the executable files. The two versions of Addition are exported by name; each identifier will be embedded in each executable file referencing it.

Where do you find the names to be inserted in the module-definition file? In the linker-generated map file.

Unfortunately, after doing a moderate amount of work with module definition files, you’ll find yourself reading mangled names in their native form. It’s a bittersweet day in any developer’s life.

Exporting Classes

There is an alternative to mangled name madness. Microsoft provides a keyword to publish the contents of an entire class. However, it’s the least efficient way to export your member functions, and it carries all of the performance hits discussed previously (bloated executables and longer load time).

Class exporting is done by embedding a declaration specification between the keyword class and the name of the class (see Listing 33.2). Using the export directive __declspec (dllexport) on the class declaration tells the compiler and linker that all member functions—public, protected, and private—should be published in the import file.

Listing 33.2 Exporting Class by Class


class __declspec (dllexport) CMyWindow: public CWnd
{
    CMyWindow();
    ~CMyWindow();
    // Generated message map functions
protected:
    //{{AFX_MSG(CMyWindow)
    afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
    //}}AFX_MSG
    DECLARE_MESSAGE_MAP()
};

It makes little sense, however, to publish all members—for example, private members can only be referenced by other members of your class and friend classes. Unless a friend class exists outside your DLL (a dubious design decision), this is a waste of an export, and a decrease in performance. Avoid class exporting in all but the simplest of cases.

What Goes Around, Comes Around

So far, I’ve only talked about exporting from DLLs. Consider the perspective of executables using your DLL. Rather than exporting identifiers, they will be importing them.

These executables should see your sample class as

class __declspec (dllimport) CMyWindow: public CWnd

As the DLL developer, you are left with one of two options: Maintain a duplicate copy of the class header file using __declspec (dllimport) (not a good long-term solution), or use the preprocessor to present the compiler with what it expects to see.

To use the preprocessor, define a special symbol in the project settings of your extension DLL: MY_DLL_INTERNALS. Then, in each header file, use the following construct:

#ifdef MY_DLL_INTERNALS
#undef EXPORTMODE
#define EXPORTMODE __declspec (dllexport)  // Export the identifiers
#else
#undef EXPORTMODE
#define EXPORTMODE __declspec (dllimport) // Import the identifiers
#endif

Then modify each class declaration to use the following form:

class EXPORTMODE CMyWindow: public CWnd

When the include file is read during the extension DLL compile, MY_DLL_INTERNALS will be defined, and the class will be exported. But when users of your DLL compile, MY_DLL_INTERNALS will be undefined and the class will be imported. This trick can also be used to export functions and data members.

Exporting Explicit Functions

You can cause a function to be exported by explicitly including the __declspec (dllexport) keyword on both its declaration and definition. This directs the compiler and linker to export the function in the import library. Unfortunately, the function is exported by name, not ordinal. You can override the export in the module definition file, but, if you’re going to the trouble of looking up the mangled name anyway, why bother using the export keyword? Listing 33.3 contains an example of exporting member by member.

Listing 33.3 Exporting Member by Member


// Declaration of class
 class CMyClass : public CObject
{
    public:
        EXPORTMODE CMyClass();
    private:
        void DoSomething (int, void*);
};
// Implementation of members
EXPORTMODE CMyClass::CMyClass()
{
    ...
}
void CMyClass::DoSomething (int nValue, void* pData)
{

}

Should you forget to reset EXPORTMODE to __declspec (dllexport) in the implementation (CPP) files, the compiler will let you know in a hurry. __declspec (dllimport) tells the compiler that the actual function will be provided externally; when the compiler sees an actual definition, it knows something funky is going on.

Exporting Data

Most AFX DLLs only export functions and classes. But sometimes it is important to expose a data member to a caller. Much like functions, data members can be exported and imported using the __declspec (dllexport) and __declspec (dllimport) keywords.

EXPORTMODE CString g_strPublic;



Note that the compiler treats a __declspec (dllimport) as an implicit extern declaration. In other words, because dllimport tells the compiler the data member is defined in another executable, the compiler knows it has external scope and treats what would otherwise be a definition as an external declaration. However, it doesn’t hurt to include an explicit extern if you’re uncomfortable with that.

Exporting MFC Data

MFC dynamic objects (CObject) and message maps (CCmdTarget) depend on the export of certain data members you might not even know you had. In some cases, failure to export these members results in a link error. In other cases, executables will not work properly, possibly causing MFC internal ASSERTs or GPFs. These can be non-obvious and hard to track down.

When you include the keywords DECLARE_DYNAMIC, DECLARE_DYNCREATE, or DECLARE_SERIAL in your CObject-derived class, you declare a data member of type RuntimeClass. CRuntimeClass is a struct containing the class name, schema number (for CArchive), a pointer to the base class information, and a function pointer to create a new object. Inclusion of the corresponding IMPLEMENT_ macro causes this data member to be defined. When MFC needs to create an object of that type (from serialization or dynamic creation), it uses the data in the struct to do the creation. If this structure isn’t exported properly, modules using your DLL won’t be able to use your classes.

When you include the keyword DECLARE_MESSAGE_MAP in your CCmdTarget-derived class, a similar process happens. In this case, two static members are added to your class: an AFX_MSGMAP struct and an array of AFX_MSGMAP_ENTRY structs. The BEGIN_MESSAGE_MAP macros cause these members to be defined. If the AFX_MSGMAP member isn’t exported, your window class will fail to function properly when used by external modules.

On the surface, this poses a problem because the declaration and definition of these data members are embedded deep within the MFC macros. Microsoft’s designers anticipated this problem, however, and have included a clever workaround.

Each data member created by an MFC macro (DECLARE_xxx or IMPLEMENT_xxx) contains the keyword AFX_DATA. By default, the AFX header files define AFX_DATA to be blank. If, however, you define AFX_DATA to be __declspec (dllexport) or __declspec (dllimport), the data members are correctly exported (or imported). Listing 33.4 contains an example of exporting MFC-generated data members.

Listing 33.4 Exporting MFC-Generated Data Members


// Top of header file
#ifdef MY_DLL_INTERNALS
#undef AFX_DATA
#define AFX_DATA  __declspec (dllexport)
#else
#undef AFX_DATA
#define AFX_DATA  __declspec (dllimport)
#endif
class CMyWindow : public CWnd
{
    public:
        CMyWindow();
    // Generated message map functions
    protected:
    //{{AFX_MSG(CMyWindow)
    afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
    //}}AFX_MSG
    DECLARE_MESSAGE_MAP()
};

#undef AFX_DATA

This technique will export the MFC-generated symbols by name. You can override this behavior by placing the mangled name in the module definition file and using an explicit ordinal value combined with the NONAME keyword.

Exporting the Destructor

Exporting destructors is a confusing proposition. Four different destructor functions are created by the compiler: the normal destructor, a vector destructor, a vector deleting destructor, and a static deleting destructor. Listing 33.5 gives examples of destructor usage.

Listing 33.5 Destructor Usage


{
    CFoo* pFoo = new CFoo
    CFoo* pFooArray = new CFoo[4];
    CFoo scalarFoo;
    CFoo aFoo[10];
    delete pFoo;         // Invokes the scalar deleting destructor
    delete [] pFooArray; // Invokes the vector deleting destructor
    // scalarFoo goes out of scope and the normal
    // destructor is invoked.
   // aFoo goes out of scope and the normal vector
   // destructor is invoked.
}

The compiler invokes the deleting destructors when you call the delete operator on a pointer. The vector destructor is used to delete arrays, and scalar destructors are used on single objects. The normal destructor and normal vector destructors are invoked when a stack-based object or array of objects goes out of scope.

Do not export the deleting destructors. The deleting destructors free the memory associated with the object being deleted. Because the calling executable might be using a different memory manager than your DLL, exporting these destructors can cause hard-to-understand GPFs or ASSERTs. These GPFs are caused when memory allocated by one memory manager (the executable’s) is freed by another memory manager (the DLLs). In the absence of a deleting destructor, the compiler and linker of the calling module will synthesize a deleting destructor to release the memory.

Visual Studio versions prior to 5 had a problem in which a normal vector destructor would not be created by the compiler and linker unless vector operations took place. This causes the wrong destructor to be invoked in calling executables when an array of objects is freed.

If you expect your DLL to be used with executables created with older versions of Visual Studio, you should consider the workaround given in Listing 33.6.

Listing 33.6 Destructor Usage


static void _ForceVectorDelete()
{
#ifdef _DEBUG
    ASSERT(FALSE);  // never called
#endif
    new CFoo [2];
    new CBar [2];
}
void (*_ForceVectorDeleteInclusion)() = &ForceVectorDelete;

ForceVectorDelete is never invoked; however, the vector new syntax (new CFoo[2]) causes the compiler to synthesize a vector destructor for each class. The ForceVectorDeleteInclusion function pointer keeps the optimizing linker from eliminating ForceVectorDelete from the executable.

Export Toolkit include Files

The set of include files in Listings 33.7 through 33.10 automates dllimport/dllexport selection in source and include files.

Listing 33.7 MakeSymbolsInternal.h


#undef AFX_DATA
#define AFX_DATA __declspec (dllexport)
#undef EXPORTMODE
#define EXPORTMODE __declspec (dllexport)

Listing 33.8 MakeSymbolsExternal.h


#undef AFX_DATA
#define AFX_DATA __declspec (dllimport)
#undef EXPORTMODE
#define EXPORTMODE __declspec (dllimport)

Listing 33.9 ResetSymbols.h


#undef AFX_DATA
#undef EXPORTMODE

Listing 33.10 StartOfCode.h


#undef AFX_DATA
#define AFX_DATA __declspec (dllexport)
#undef EXPORTMODE
#define EXPORTMODE __declspec (dllexport)

Notice how these files don’t prevent multiple inclusion. They are designed to be included multiply. At the top of your include files, use the following:

#ifdef MY_DLL_INTERNALS
#include “MakeSymbolsInternal.h”
#else
#include “MakeSymbolsExternal.h”
#endif

// Include exported classes, data, and other definitions here

#include “ResetSymbols.h”

Now, in your implementation files, include StartOfCode.h as the last include file. This causes symbols to be reset to dllexport so the proper executable code is generated.



What to Export

Now that you’ve learned about the techniques and rather baroque rules for exporting functions, let’s take a look at guidelines for selecting functions to be exported. I’ll assume that you’ve bowed to the inevitable and are exporting member by member, rather than class by class.

Of course, if you’re developing your DLL for internal use only, you can always resort to the link-whoops method. The link-whoops method consists of compiling and linking a program that uses your DLL, and then adding any unresolved external functions to the DEF file. You run the risk, however, of missing exports that aren’t used at the current time but might be needed at a later date.

Here’s a checklist for determining what to export:

  Export any nonmember functions and/or static class members.
  Export any applicable constructors. If the class is dynamically creatable (DYNCREATE), be sure to export the default constructor.
  Export the destructor. If other executables can use your classes, they should be able to destroy them. Be careful not to export any deleting destructors and to abide by vector destruction rules.
  Export public methods. If you didn’t want class users to call a method, why make it public?
  Export protected methods. Protected members can be invoked by derived classes and friends, and thus should be available from outside the DLL.
  Export virtual functions. If derived classes are expected or allowed to override a member function, the default method should be available to them.
  Do not export inline functions.
  Export static data members, such as message map entries (AFX_MSGMAP) or runtime class information (CRuntimeClass).

Other DLL Issues

Although exporting is certainly a large issue in using DLLs, other issues deserve consideration as well.

AfxLoadLibrary and AfxFreeLibrary

If your DLL gets dynamically loaded and unloaded (a very uncommon design), be sure to use the functions AfxLoadLibrary and AfxFreeLibrary to load and unload it (instead of LoadLibrary and FreeLibrary). The AFX versions of these functions lock the MFC internals so that the DLL can be linked into (or out of) the module list. Because most DLLs are loaded at runtime rather than dynamically linked, this is rarely an issue.

Designing for Extensibility and Reuse

There are some simple rules to obey if your DLL will be used by multiple projects. These rules help smooth bug fixes, feature enhancements, and versioning.

First, decide early whether your DLL file-naming scheme will include a version number. If you embed a version number within the name (such as MFC42.dll), major upgrades will be much easier. Because changing the DLL name requires a relink of any executables that use your DLL, you can safely make class changes at that time as well.

After your DLL has been shipped to customers, you are much more limited in the changes you can make without requiring a version change and/or a target recompile.

First, do not add functionality to your DLL. This is called blind-reving (blind revisioning), and causes uncounted headaches in the field. Blind-reving involves changing the behavior and/or exports of a module without changing the version number. Now, instead of simply checking a version number, installation utilities and technical support personnel must check the version number, file date and time, and file size. If you’ve ever received the message “Ordinal not found in DLL,” you’ve been blind-reved.

Do not add or remove virtual functions from exported classes, or any base classes. Don’t even reorder the virtual functions in a header file. Similarly, don’t change the derivation chain of a class.

C++ uses a construct called a vtable to enable polymorphic behavior. Put simply, the vtable is a list of all virtual functions in a class and any base classes. If you write code to call a virtual function, the compiler looks up the address of the most derived implementation of that function in the vtable, and then invokes it. If you reorder the virtual functions in a class, the vtable changes. Normally, this is not a problem; however, if the vtable being used by your DLL is different from the vtable being used by an executable, chaos results. Executables compiled with the old header files will have the old vtable layout and will invoke the wrong function in classes created by your DLL.

If you need to add or remove virtual functions, change the version number and require your users to recompile.

A less obvious but similar problem area is inline functions. If a bug fix requires a change to an inline function, all class users must recompile to receive the fix. Again, the safest way to ensure that your customers recompile is to change the version number (and name) of the DLL.

Resource Location

One of the chief advantages of DLLs over static libraries is that they come packaged with their own resources. This can cause problems if executables linking with those DLLs expect to retrieve those resources.

All of the primary resource functions (LoadString, LoadBitmap, LoadCursor, LoadIcon, DialogBox, and so on) require an instance handle to identify the module that contains the resource. If the resource is stored in a DLL, you might have problems loading it.

There are several ways to solve this problem. One technique is to design your applications so that no module refers to the resources of another module. This can prove difficult, especially if you use the string table to store error message strings. If, however, you are a tool vendor, your DLL should be reasonably self-contained anyway.

A second strategy is to provide an exported function in each DLL that returns its instance handle. This pushes the problem onto the user of your DLL.

The best strategy, although the most problematic, is to use AfxFindResourceHandle() and AfxLoadString(). These functions properly traverse the list of registered AFX DLLs, searching for the first occurrence of a given resource type and ID. MFC uses these functions internally to find the correct dialog boxes, icons, and other resources to be used.

AfxFindResourceHandle() first checks the current executable for the resource. Failing that, it traverses all AFX DLLs in reverse load order (most recently loaded first). It then checks any MFC language DLL, and finally the MFC DLLs themselves.



AfxLoadString does the same thing, but loads the string contents into a provided buffer. Curiously, CString::LoadString does not take advantage of AfxLoadString. CString::LoadString merely checks the current executable, making string-table access through CString objects more tedious.

Although AfxFindResourceHandle and AfxLoadString make accessing resources easier, there is a sharp downside. Because each function stops as soon as it finds a resource matching the ID and type, resource identifiers in DLLs cannot collide. A resource collision would cause your program to display the wrong dialog box, icon, cursor, string, and so on.

Visual Studio makes this problematic because each resource file starts its identifiers at 100 and works upwards. A good solution is to create a spreadsheet or text file containing resource ranges assigned to each DLL. That way, different components can coexist.


Tip:  

String table entries are stored in groups of 16 strings, so be sure that your resource ranges don’t overlap too closely. If one DLL has a range from 1-999 and another has the range 1000-1999, strings in the overlap range (992-1007) effectively collide. This is a quirk of the way strings are stored in resource and executable files.


Multiple Module Definition Files

Today’s applications are expected to run in different modes and different locales. The Unicode standard provides double-byte character support for languages such as Kanji that cannot fit their character set into an eight-bit value. Under most circumstances, Visual Studio supports seamless movement between single-byte (ANSI) and double-byte (Unicode) builds. This is done through the header file tchar.h. TChar contains an alias to every runtime function that takes a character or string parameter. If the UNICODE preprocessor variable is defined, wide-character versions are invoked; otherwise, single-byte versions are invoked.

Unfortunately, character size affects compiler name-mangling. Functions that take a size-independent character pointer (TCHAR*, LPCTSR, and so on) have different mangled names when UNICODE is defined than when it isn’t. Because the linker doesn’t support conditional compilation in module definition files (that is, you can’t embed an #ifdef UNICODE in the DEF file), you’re stuck with maintaining multiple files.

This isn’t too much of a problem at first blush. Only a small percentage of functions that generally take or return character pointers are arguments, and the linker points out when your export entries are incorrect.

However, if you attempt to add a second module definition file into your project, Visual Studio balks because it can’t resolve which file to use. This requires you to enter the correct module-definition file directly into the linker Project Options edit window by using the /Def:<filename> linker directive. This has the unfortunate effect of removing the module definition file from the project dependencies. Put another way, a change to the module definition file will not cause Visual Studio to relink the project.

These same rules apply if your DLL requires different module definition files for debug and release builds. Each MFC DLL has four separate module definition files: Unicode release, Unicode Debug, ANSI Release, and ANSI Debug.

Load Addresses and the Linker

One of the oft-cited reasons for using DLLs is to save memory. After all, if two executables are running and use the same DLL, why would the operating system store two copies of the code?

This theory works as long as everybody plays by the rules. When the linker creates an executable module, it is required to “base” the executable at some starting point in virtual memory. The linker creates default values for fixup table entries relative to this point. By default, the starting point for executable files (EXEs) is hex 00400000. For DLLs, the starting point is 10000000.

When an executable is loaded, the Windows PE loader must find a place in virtual memory where each DLL can be loaded. If the load address specified is available, it is loaded at that point. If not, an arbitrary point in virtual memory is chosen, and all the fixup table entries in the executable are changed to conform to the new location. Two significant performance problems appear when this happens.

First, the load time of your executable increases dramatically. Every fixup address in the loaded DLL gets rewritten, and every module that uses that address rewrites its import table. This takes time.

More critically, the executable code of the DLL is no longer sharable. When a second or third process is started that references the DLL, the code will not be shared, but rather rewritten to memory. Here’s why:

The loader takes advantage of a virtual-memory feature called copy-on-write. When the copy-on-write flag is set on a page of virtual memory, any attempt to write to that page causes the operating system to silently create a new copy of the page in physical memory. References to that page in Process A will no longer refer to the same page as Process B.

When an executable is loaded, the copy-on-write bit is set on all code pages. When a second copy of the executable is loaded, the loader receives the virtual address of the first copy of the page. If, however, the loader needs to change the base address because of a conflict, the process of writing new fixup offsets to the code causes the operating system to create a second (or third, or fourth) copy of each page. Thus, the code in the DLL is no longer shared between the processes.

The solution is to ensure that each DLL in your project loads at a unique address. If no address conflicts occur, the loader does not rewrite the executable, and a single copy of the code exists in memory. Be aware that system DLLs, network and hardware drivers, shell extensions, common control DLLs, and other modules are mapped into your process space at various times. Most vendors have changed to a unique load address. When you run your application in the debugger, check to ensure that no conflicts occur. If a conflict is detected, a message will be output to the debug console:

LDR: Dll COMCTL32.dll base 71030000 relocated due to collision with E:\WINNT40\system32\SHLWAPI.dll

In this case, two Microsoft DLLs, the common control DLL and the Lightweight Shell API DLL, are conflicting. Other than complain to the tool vendor that the product should be more professional, there’s not a lot you can do.

To change the load address of your DLL, use the linker /BASE directive. This directive is set through the Base Address window of the Output page of the linker options under Project Settings. The directive has two forms. First, you can insert a hard-coded address into the window (such as 0×10050000). A better way is to use a load-address text file. This form of the /BASE directive specifies a text filename and module name, using the following syntax: @<textfilename>,<modulename>. The linker reads the text file, looks for a line that starts with <modulename>, and uses the starting address and length found on that line to base the executable. Listing 33.11 contains a sample load address file.

Listing 33.11 Sample Load Address File


ControlDll      0×10300000 0×00100000
ConfigUtil      0×10400000 0×00200000
Server          0×10600000 0×00100000

So, for the server project, the /Base setting would be @LoadAddr.txt,Server. The control DLL would use @LoadAddr.txt,ControlDLL.

This form of the /Base directive gives you the ability to change the load addresses at a single location. It also helps you to form a mental map of what your process space looks like.

Summary

Writing an extension DLL can be a daunting task at first blush. It takes quite a bit of work to get the correct structure in place, and subsequent code changes take longer to complete.

The key to DLL development is planning, planning, and more planning. Don’t rely on link errors to warn about unexported functions; have an export list ready prior to the completion of the code. The most common DLL development problem is the failure to export the correct functions. Unfortunately, this generally manifests itself during the second project that uses the DLL. This necessitates a new release of the DLL and eliminates some of the reusability benefits.

Create a strategy for resource ID ranges, module load addresses, and file naming schemes. Circulate these documents widely to the developers using your DLL. Finally, use the automation headers presented in this chapter (MakeSymbolsInternal, MakeSymbolsExternal, and StartOfCode). They will significantly reduce startup time by eliminating import/export convention problems.



Mangled Names

You’ve probably seen them buried deep inside a map file: an identifier that kind of looks like one of your function names after being run through a blender.

If the following function appears in a C source file,

void StraightCFunction (int* pInput, const char* pszOutput)

the following symbol appears in the object and map files:

_StraightCFunction

This function is suitable for inclusion in the module definition file. Take the same function and insert it into a CPP source file, and you get the following:

?StraightCFunction@@YAXPAHPBD@Z

What’s the stuff following the name? This is compiler-speak for the argument list and return types. In standard C, it is illegal to have two functions or variables with the same name (unless one or both are static scope). So the compiler merely refers to the symbol by its textual name, with an underscore prepended. C++, however, supports function overloading. Function overloading enables you to have two externally visible functions with the same name, provided their argument lists are different. Thus, in a C++ file, you could have the following:

int Addition (int nOperand1, int nOperand2);
long Addition (long lOperand1, long lOperand2);

To avoid name conflicts, and to ensure that the linker enforces strong typing, the compiler encodes (mangles) the parameter types, return type, and any qualifiers (such as const) into a single name.

This phenomenon is what leads to object and library incompatibilities between compiler vendors because each vendor uses a slightly different encoding scheme. And although compiler geeks try to put a positive spin on it (they call it name decoration), it complicates the DLL development process immensely. Listing 33.1 shows a sample module definition file that includes compiler-mangled names.

Listing 33.1 Sample Module Definition File


;
; Sample Module Defintion File
;
LIBRARY      “afxsamp1”
DESCRIPTION  ‘Afx Sample Windows Dynamic Link Library’
EXPORTS
?StraightCPPFunction@@YAXPAHPBD@Z @1000 NONAME ; Exported by ordinal
StraightCFunction                @1001 NONAME  ; Exported by ordinal
?Addition@@YAHHH@Z                             ; Exported by name
?Addition@@YAJJJ@Z                             ; Exported by name

In this example, the StraightCFunction and StraightCPPFunction are exported by ordinal. The @xxxx assigns a unique numeric value to each identifier; the NONAME qualifier explicitly removes the textual name from the executable files. The two versions of Addition are exported by name; each identifier will be embedded in each executable file referencing it.

Where do you find the names to be inserted in the module-definition file? In the linker-generated map file.

Unfortunately, after doing a moderate amount of work with module definition files, you’ll find yourself reading mangled names in their native form. It’s a bittersweet day in any developer’s life.

Exporting Classes

There is an alternative to mangled name madness. Microsoft provides a keyword to publish the contents of an entire class. However, it’s the least efficient way to export your member functions, and it carries all of the performance hits discussed previously (bloated executables and longer load time).

Class exporting is done by embedding a declaration specification between the keyword class and the name of the class (see Listing 33.2). Using the export directive __declspec (dllexport) on the class declaration tells the compiler and linker that all member functions—public, protected, and private—should be published in the import file.

Listing 33.2 Exporting Class by Class


class __declspec (dllexport) CMyWindow: public CWnd
{
    CMyWindow();
    ~CMyWindow();
    // Generated message map functions
protected:
    //{{AFX_MSG(CMyWindow)
    afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
    //}}AFX_MSG
    DECLARE_MESSAGE_MAP()
};

It makes little sense, however, to publish all members—for example, private members can only be referenced by other members of your class and friend classes. Unless a friend class exists outside your DLL (a dubious design decision), this is a waste of an export, and a decrease in performance. Avoid class exporting in all but the simplest of cases.

What Goes Around, Comes Around

So far, I’ve only talked about exporting from DLLs. Consider the perspective of executables using your DLL. Rather than exporting identifiers, they will be importing them.

These executables should see your sample class as

class __declspec (dllimport) CMyWindow: public CWnd

As the DLL developer, you are left with one of two options: Maintain a duplicate copy of the class header file using __declspec (dllimport) (not a good long-term solution), or use the preprocessor to present the compiler with what it expects to see.

To use the preprocessor, define a special symbol in the project settings of your extension DLL: MY_DLL_INTERNALS. Then, in each header file, use the following construct:

#ifdef MY_DLL_INTERNALS
#undef EXPORTMODE
#define EXPORTMODE __declspec (dllexport)  // Export the identifiers
#else
#undef EXPORTMODE
#define EXPORTMODE __declspec (dllimport) // Import the identifiers
#endif

Then modify each class declaration to use the following form:

class EXPORTMODE CMyWindow: public CWnd

When the include file is read during the extension DLL compile, MY_DLL_INTERNALS will be defined, and the class will be exported. But when users of your DLL compile, MY_DLL_INTERNALS will be undefined and the class will be imported. This trick can also be used to export functions and data members.

Exporting Explicit Functions

You can cause a function to be exported by explicitly including the __declspec (dllexport) keyword on both its declaration and definition. This directs the compiler and linker to export the function in the import library. Unfortunately, the function is exported by name, not ordinal. You can override the export in the module definition file, but, if you’re going to the trouble of looking up the mangled name anyway, why bother using the export keyword? Listing 33.3 contains an example of exporting member by member.

Listing 33.3 Exporting Member by Member


// Declaration of class
 class CMyClass : public CObject
{
    public:
        EXPORTMODE CMyClass();
    private:
        void DoSomething (int, void*);
};
// Implementation of members
EXPORTMODE CMyClass::CMyClass()
{
    ...
}
void CMyClass::DoSomething (int nValue, void* pData)
{

}

Should you forget to reset EXPORTMODE to __declspec (dllexport) in the implementation (CPP) files, the compiler will let you know in a hurry. __declspec (dllimport) tells the compiler that the actual function will be provided externally; when the compiler sees an actual definition, it knows something funky is going on.

Exporting Data

Most AFX DLLs only export functions and classes. But sometimes it is important to expose a data member to a caller. Much like functions, data members can be exported and imported using the __declspec (dllexport) and __declspec (dllimport) keywords.

EXPORTMODE CString g_strPublic;



Note that the compiler treats a __declspec (dllimport) as an implicit extern declaration. In other words, because dllimport tells the compiler the data member is defined in another executable, the compiler knows it has external scope and treats what would otherwise be a definition as an external declaration. However, it doesn’t hurt to include an explicit extern if you’re uncomfortable with that.

Exporting MFC Data

MFC dynamic objects (CObject) and message maps (CCmdTarget) depend on the export of certain data members you might not even know you had. In some cases, failure to export these members results in a link error. In other cases, executables will not work properly, possibly causing MFC internal ASSERTs or GPFs. These can be non-obvious and hard to track down.

When you include the keywords DECLARE_DYNAMIC, DECLARE_DYNCREATE, or DECLARE_SERIAL in your CObject-derived class, you declare a data member of type RuntimeClass. CRuntimeClass is a struct containing the class name, schema number (for CArchive), a pointer to the base class information, and a function pointer to create a new object. Inclusion of the corresponding IMPLEMENT_ macro causes this data member to be defined. When MFC needs to create an object of that type (from serialization or dynamic creation), it uses the data in the struct to do the creation. If this structure isn’t exported properly, modules using your DLL won’t be able to use your classes.

When you include the keyword DECLARE_MESSAGE_MAP in your CCmdTarget-derived class, a similar process happens. In this case, two static members are added to your class: an AFX_MSGMAP struct and an array of AFX_MSGMAP_ENTRY structs. The BEGIN_MESSAGE_MAP macros cause these members to be defined. If the AFX_MSGMAP member isn’t exported, your window class will fail to function properly when used by external modules.

On the surface, this poses a problem because the declaration and definition of these data members are embedded deep within the MFC macros. Microsoft’s designers anticipated this problem, however, and have included a clever workaround.

Each data member created by an MFC macro (DECLARE_xxx or IMPLEMENT_xxx) contains the keyword AFX_DATA. By default, the AFX header files define AFX_DATA to be blank. If, however, you define AFX_DATA to be __declspec (dllexport) or __declspec (dllimport), the data members are correctly exported (or imported). Listing 33.4 contains an example of exporting MFC-generated data members.

Listing 33.4 Exporting MFC-Generated Data Members


// Top of header file
#ifdef MY_DLL_INTERNALS
#undef AFX_DATA
#define AFX_DATA  __declspec (dllexport)
#else
#undef AFX_DATA
#define AFX_DATA  __declspec (dllimport)
#endif
class CMyWindow : public CWnd
{
    public:
        CMyWindow();
    // Generated message map functions
    protected:
    //{{AFX_MSG(CMyWindow)
    afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
    //}}AFX_MSG
    DECLARE_MESSAGE_MAP()
};

#undef AFX_DATA

This technique will export the MFC-generated symbols by name. You can override this behavior by placing the mangled name in the module definition file and using an explicit ordinal value combined with the NONAME keyword.

Exporting the Destructor

Exporting destructors is a confusing proposition. Four different destructor functions are created by the compiler: the normal destructor, a vector destructor, a vector deleting destructor, and a static deleting destructor. Listing 33.5 gives examples of destructor usage.

Listing 33.5 Destructor Usage


{
    CFoo* pFoo = new CFoo
    CFoo* pFooArray = new CFoo[4];
    CFoo scalarFoo;
    CFoo aFoo[10];
    delete pFoo;         // Invokes the scalar deleting destructor
    delete [] pFooArray; // Invokes the vector deleting destructor
    // scalarFoo goes out of scope and the normal
    // destructor is invoked.
   // aFoo goes out of scope and the normal vector
   // destructor is invoked.
}

The compiler invokes the deleting destructors when you call the delete operator on a pointer. The vector destructor is used to delete arrays, and scalar destructors are used on single objects. The normal destructor and normal vector destructors are invoked when a stack-based object or array of objects goes out of scope.

Do not export the deleting destructors. The deleting destructors free the memory associated with the object being deleted. Because the calling executable might be using a different memory manager than your DLL, exporting these destructors can cause hard-to-understand GPFs or ASSERTs. These GPFs are caused when memory allocated by one memory manager (the executable’s) is freed by another memory manager (the DLLs). In the absence of a deleting destructor, the compiler and linker of the calling module will synthesize a deleting destructor to release the memory.

Visual Studio versions prior to 5 had a problem in which a normal vector destructor would not be created by the compiler and linker unless vector operations took place. This causes the wrong destructor to be invoked in calling executables when an array of objects is freed.

If you expect your DLL to be used with executables created with older versions of Visual Studio, you should consider the workaround given in Listing 33.6.

Listing 33.6 Destructor Usage


static void _ForceVectorDelete()
{
#ifdef _DEBUG
    ASSERT(FALSE);  // never called
#endif
    new CFoo [2];
    new CBar [2];
}
void (*_ForceVectorDeleteInclusion)() = &ForceVectorDelete;

ForceVectorDelete is never invoked; however, the vector new syntax (new CFoo[2]) causes the compiler to synthesize a vector destructor for each class. The ForceVectorDeleteInclusion function pointer keeps the optimizing linker from eliminating ForceVectorDelete from the executable.

Export Toolkit include Files

The set of include files in Listings 33.7 through 33.10 automates dllimport/dllexport selection in source and include files.

Listing 33.7 MakeSymbolsInternal.h


#undef AFX_DATA
#define AFX_DATA __declspec (dllexport)
#undef EXPORTMODE
#define EXPORTMODE __declspec (dllexport)

Listing 33.8 MakeSymbolsExternal.h


#undef AFX_DATA
#define AFX_DATA __declspec (dllimport)
#undef EXPORTMODE
#define EXPORTMODE __declspec (dllimport)

Listing 33.9 ResetSymbols.h


#undef AFX_DATA
#undef EXPORTMODE

Listing 33.10 StartOfCode.h


#undef AFX_DATA
#define AFX_DATA __declspec (dllexport)
#undef EXPORTMODE
#define EXPORTMODE __declspec (dllexport)

Notice how these files don’t prevent multiple inclusion. They are designed to be included multiply. At the top of your include files, use the following:

#ifdef MY_DLL_INTERNALS
#include “MakeSymbolsInternal.h”
#else
#include “MakeSymbolsExternal.h”
#endif

// Include exported classes, data, and other definitions here

#include “ResetSymbols.h”

Now, in your implementation files, include StartOfCode.h as the last include file. This causes symbols to be reset to dllexport so the proper executable code is generated.



What to Export

Now that you’ve learned about the techniques and rather baroque rules for exporting functions, let’s take a look at guidelines for selecting functions to be exported. I’ll assume that you’ve bowed to the inevitable and are exporting member by member, rather than class by class.

Of course, if you’re developing your DLL for internal use only, you can always resort to the link-whoops method. The link-whoops method consists of compiling and linking a program that uses your DLL, and then adding any unresolved external functions to the DEF file. You run the risk, however, of missing exports that aren’t used at the current time but might be needed at a later date.

Here’s a checklist for determining what to export:

  Export any nonmember functions and/or static class members.
  Export any applicable constructors. If the class is dynamically creatable (DYNCREATE), be sure to export the default constructor.
  Export the destructor. If other executables can use your classes, they should be able to destroy them. Be careful not to export any deleting destructors and to abide by vector destruction rules.
  Export public methods. If you didn’t want class users to call a method, why make it public?
  Export protected methods. Protected members can be invoked by derived classes and friends, and thus should be available from outside the DLL.
  Export virtual functions. If derived classes are expected or allowed to override a member function, the default method should be available to them.
  Do not export inline functions.
  Export static data members, such as message map entries (AFX_MSGMAP) or runtime class information (CRuntimeClass).

Other DLL Issues

Although exporting is certainly a large issue in using DLLs, other issues deserve consideration as well.

AfxLoadLibrary and AfxFreeLibrary

If your DLL gets dynamically loaded and unloaded (a very uncommon design), be sure to use the functions AfxLoadLibrary and AfxFreeLibrary to load and unload it (instead of LoadLibrary and FreeLibrary). The AFX versions of these functions lock the MFC internals so that the DLL can be linked into (or out of) the module list. Because most DLLs are loaded at runtime rather than dynamically linked, this is rarely an issue.

Designing for Extensibility and Reuse

There are some simple rules to obey if your DLL will be used by multiple projects. These rules help smooth bug fixes, feature enhancements, and versioning.

First, decide early whether your DLL file-naming scheme will include a version number. If you embed a version number within the name (such as MFC42.dll), major upgrades will be much easier. Because changing the DLL name requires a relink of any executables that use your DLL, you can safely make class changes at that time as well.

After your DLL has been shipped to customers, you are much more limited in the changes you can make without requiring a version change and/or a target recompile.

First, do not add functionality to your DLL. This is called blind-reving (blind revisioning), and causes uncounted headaches in the field. Blind-reving involves changing the behavior and/or exports of a module without changing the version number. Now, instead of simply checking a version number, installation utilities and technical support personnel must check the version number, file date and time, and file size. If you’ve ever received the message “Ordinal not found in DLL,” you’ve been blind-reved.

Do not add or remove virtual functions from exported classes, or any base classes. Don’t even reorder the virtual functions in a header file. Similarly, don’t change the derivation chain of a class.

C++ uses a construct called a vtable to enable polymorphic behavior. Put simply, the vtable is a list of all virtual functions in a class and any base classes. If you write code to call a virtual function, the compiler looks up the address of the most derived implementation of that function in the vtable, and then invokes it. If you reorder the virtual functions in a class, the vtable changes. Normally, this is not a problem; however, if the vtable being used by your DLL is different from the vtable being used by an executable, chaos results. Executables compiled with the old header files will have the old vtable layout and will invoke the wrong function in classes created by your DLL.

If you need to add or remove virtual functions, change the version number and require your users to recompile.

A less obvious but similar problem area is inline functions. If a bug fix requires a change to an inline function, all class users must recompile to receive the fix. Again, the safest way to ensure that your customers recompile is to change the version number (and name) of the DLL.

Resource Location

One of the chief advantages of DLLs over static libraries is that they come packaged with their own resources. This can cause problems if executables linking with those DLLs expect to retrieve those resources.

All of the primary resource functions (LoadString, LoadBitmap, LoadCursor, LoadIcon, DialogBox, and so on) require an instance handle to identify the module that contains the resource. If the resource is stored in a DLL, you might have problems loading it.

There are several ways to solve this problem. One technique is to design your applications so that no module refers to the resources of another module. This can prove difficult, especially if you use the string table to store error message strings. If, however, you are a tool vendor, your DLL should be reasonably self-contained anyway.

A second strategy is to provide an exported function in each DLL that returns its instance handle. This pushes the problem onto the user of your DLL.

The best strategy, although the most problematic, is to use AfxFindResourceHandle() and AfxLoadString(). These functions properly traverse the list of registered AFX DLLs, searching for the first occurrence of a given resource type and ID. MFC uses these functions internally to find the correct dialog boxes, icons, and other resources to be used.

AfxFindResourceHandle() first checks the current executable for the resource. Failing that, it traverses all AFX DLLs in reverse load order (most recently loaded first). It then checks any MFC language DLL, and finally the MFC DLLs themselves.



AfxLoadString does the same thing, but loads the string contents into a provided buffer. Curiously, CString::LoadString does not take advantage of AfxLoadString. CString::LoadString merely checks the current executable, making string-table access through CString objects more tedious.

Although AfxFindResourceHandle and AfxLoadString make accessing resources easier, there is a sharp downside. Because each function stops as soon as it finds a resource matching the ID and type, resource identifiers in DLLs cannot collide. A resource collision would cause your program to display the wrong dialog box, icon, cursor, string, and so on.

Visual Studio makes this problematic because each resource file starts its identifiers at 100 and works upwards. A good solution is to create a spreadsheet or text file containing resource ranges assigned to each DLL. That way, different components can coexist.


Tip:  

String table entries are stored in groups of 16 strings, so be sure that your resource ranges don’t overlap too closely. If one DLL has a range from 1-999 and another has the range 1000-1999, strings in the overlap range (992-1007) effectively collide. This is a quirk of the way strings are stored in resource and executable files.


Multiple Module Definition Files

Today’s applications are expected to run in different modes and different locales. The Unicode standard provides double-byte character support for languages such as Kanji that cannot fit their character set into an eight-bit value. Under most circumstances, Visual Studio supports seamless movement between single-byte (ANSI) and double-byte (Unicode) builds. This is done through the header file tchar.h. TChar contains an alias to every runtime function that takes a character or string parameter. If the UNICODE preprocessor variable is defined, wide-character versions are invoked; otherwise, single-byte versions are invoked.

Unfortunately, character size affects compiler name-mangling. Functions that take a size-independent character pointer (TCHAR*, LPCTSR, and so on) have different mangled names when UNICODE is defined than when it isn’t. Because the linker doesn’t support conditional compilation in module definition files (that is, you can’t embed an #ifdef UNICODE in the DEF file), you’re stuck with maintaining multiple files.

This isn’t too much of a problem at first blush. Only a small percentage of functions that generally take or return character pointers are arguments, and the linker points out when your export entries are incorrect.

However, if you attempt to add a second module definition file into your project, Visual Studio balks because it can’t resolve which file to use. This requires you to enter the correct module-definition file directly into the linker Project Options edit window by using the /Def:<filename> linker directive. This has the unfortunate effect of removing the module definition file from the project dependencies. Put another way, a change to the module definition file will not cause Visual Studio to relink the project.

These same rules apply if your DLL requires different module definition files for debug and release builds. Each MFC DLL has four separate module definition files: Unicode release, Unicode Debug, ANSI Release, and ANSI Debug.

Load Addresses and the Linker

One of the oft-cited reasons for using DLLs is to save memory. After all, if two executables are running and use the same DLL, why would the operating system store two copies of the code?

This theory works as long as everybody plays by the rules. When the linker creates an executable module, it is required to “base” the executable at some starting point in virtual memory. The linker creates default values for fixup table entries relative to this point. By default, the starting point for executable files (EXEs) is hex 00400000. For DLLs, the starting point is 10000000.

When an executable is loaded, the Windows PE loader must find a place in virtual memory where each DLL can be loaded. If the load address specified is available, it is loaded at that point. If not, an arbitrary point in virtual memory is chosen, and all the fixup table entries in the executable are changed to conform to the new location. Two significant performance problems appear when this happens.

First, the load time of your executable increases dramatically. Every fixup address in the loaded DLL gets rewritten, and every module that uses that address rewrites its import table. This takes time.

More critically, the executable code of the DLL is no longer sharable. When a second or third process is started that references the DLL, the code will not be shared, but rather rewritten to memory. Here’s why:

The loader takes advantage of a virtual-memory feature called copy-on-write. When the copy-on-write flag is set on a page of virtual memory, any attempt to write to that page causes the operating system to silently create a new copy of the page in physical memory. References to that page in Process A will no longer refer to the same page as Process B.

When an executable is loaded, the copy-on-write bit is set on all code pages. When a second copy of the executable is loaded, the loader receives the virtual address of the first copy of the page. If, however, the loader needs to change the base address because of a conflict, the process of writing new fixup offsets to the code causes the operating system to create a second (or third, or fourth) copy of each page. Thus, the code in the DLL is no longer shared between the processes.

The solution is to ensure that each DLL in your project loads at a unique address. If no address conflicts occur, the loader does not rewrite the executable, and a single copy of the code exists in memory. Be aware that system DLLs, network and hardware drivers, shell extensions, common control DLLs, and other modules are mapped into your process space at various times. Most vendors have changed to a unique load address. When you run your application in the debugger, check to ensure that no conflicts occur. If a conflict is detected, a message will be output to the debug console:

LDR: Dll COMCTL32.dll base 71030000 relocated due to collision with E:\WINNT40\system32\SHLWAPI.dll

In this case, two Microsoft DLLs, the common control DLL and the Lightweight Shell API DLL, are conflicting. Other than complain to the tool vendor that the product should be more professional, there’s not a lot you can do.

To change the load address of your DLL, use the linker /BASE directive. This directive is set through the Base Address window of the Output page of the linker options under Project Settings. The directive has two forms. First, you can insert a hard-coded address into the window (such as 0×10050000). A better way is to use a load-address text file. This form of the /BASE directive specifies a text filename and module name, using the following syntax: @<textfilename>,<modulename>. The linker reads the text file, looks for a line that starts with <modulename>, and uses the starting address and length found on that line to base the executable. Listing 33.11 contains a sample load address file.

Listing 33.11 Sample Load Address File


ControlDll      0×10300000 0×00100000
ConfigUtil      0×10400000 0×00200000
Server          0×10600000 0×00100000

So, for the server project, the /Base setting would be @LoadAddr.txt,Server. The control DLL would use @LoadAddr.txt,ControlDLL.

This form of the /Base directive gives you the ability to change the load addresses at a single location. It also helps you to form a mental map of what your process space looks like.

Summary

Writing an extension DLL can be a daunting task at first blush. It takes quite a bit of work to get the correct structure in place, and subsequent code changes take longer to complete.

The key to DLL development is planning, planning, and more planning. Don’t rely on link errors to warn about unexported functions; have an export list ready prior to the completion of the code. The most common DLL development problem is the failure to export the correct functions. Unfortunately, this generally manifests itself during the second project that uses the DLL. This necessitates a new release of the DLL and eliminates some of the reusability benefits.

Create a strategy for resource ID ranges, module load addresses, and file naming schemes. Circulate these documents widely to the developers using your DLL. Finally, use the automation headers presented in this chapter (MakeSymbolsInternal, MakeSymbolsExternal, and StartOfCode). They will significantly reduce startup time by eliminating import/export convention problems.